#pragma once

#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>
#include "my_log.hpp"

template<class data_type, size_t MAX_SIZE = 20>
class block_queue
{
public:
    inline static constexpr size_t max_size() noexcept
    {
        return MAX_SIZE;
    }

    inline size_t size()
    {
        return _data_storge.size();
    }

    void push(const data_type& in)
    {
        std::unique_lock<std::mutex> ul(_mutex);
        while (size() >= MAX_SIZE)
        {
            _cond_producer.wait(ul);
        }
        _data_storge.push(in);
        #ifdef _MY_DEBUG
        lg.basic_print(level::DEBUG, std::make_tuple("producer pushed, data remain: %d", size()), true);
        if (size() == MAX_SIZE)
        {
            lg.basic_print(level::DEBUG, std::make_tuple("data full"), true);
        }
        #endif
        _cond_consumer.notify_one(); 
    }

    void push(data_type&& in)
    {
        std::unique_lock<std::mutex> ul(_mutex);
        while (size() >= MAX_SIZE)
        {
            _cond_producer.wait(ul);
        }        
        _data_storge.push(std::move(in));
        #ifdef _MY_DEBUG
        lg.basic_print(level::DEBUG, std::make_tuple("producer pushed, data remain: %d", size()), true);
        if (size() == MAX_SIZE)
        {
            lg.basic_print(level::DEBUG, std::make_tuple("data full"), true);
        }
        #endif
        _cond_consumer.notify_one();
    }

    void pop(data_type& out)
    {
        std::unique_lock<std::mutex> ul(_mutex);
        while (size() == 0)
        {
            _cond_consumer.wait(ul);
        }
        out = std::move(_data_storge.front()); 
        _data_storge.pop();
        #ifdef _MY_DEBUG
        lg.basic_print(level::DEBUG, std::make_tuple("consumer poped, data remain: %d", size()), true);
        if (size() == 0)
        {
            lg.basic_print(level::DEBUG, std::make_tuple("data empty"), true);
        }
        #endif
        _cond_producer.notify_one();    
    }
private:
    std::queue<data_type> _data_storge;
    std::mutex _mutex;
    std::condition_variable _cond_producer;
    std::condition_variable _cond_consumer;
};